iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Modern Web

Follow me! 來一場前端技能樹之旅系列 第 28

[Day 28 - 小試身手] Todolist with React (3)

  • 分享至 

  • xImage
  •  

在上一章Todolist with React (2),完成所有樣式設定後,現在就讓我們在 React 中加入 Redux,使用 React-redux 動態產生任務清單。

建立架構

將狀態資料分成 Todo 任務資料和 Filter 篩選器資料,事先建立好 Store、Reducer、Action 的檔案。

src
├── ...
├── actions
    ├── ActionTypes.js
    ├── filter.js
    ├── todos.js
├── reducer
    ├── filter.js
    ├── index.js
    ├── todos.js
├── store.js
  • actions:ActionTypes.js 負責定義 Todo 和 Filter 的所有 Action
  • reducer:index.js 會整合 Todo 和 Filter 的 Reducer
  • store.js:處理 Store

初始化 Task 資料

首先先處理渲染任務資料的部分,在 todos.js 定義出原始的任務資料 initialTasks,包含任務的名稱和狀態,方便我們產生清單。

reducer/todos.js

const initialTasks = [
  { taskName: "task1", isCompleted: false },
  { taskName: "task2", isCompleted: true },
  { taskName: "task3", isCompleted: false },
];

Todo Reducer

繼續在同一份程式碼建立 Todo Reducer,State 參數的預設值為之前定義的 initialTasks,目前尚未定義 Action,直接回傳當前原本的 State。

reducer/todos.js

import * as types from '../actions/ActionTypes';

export default function todos(state = initialTasks, action) {
  switch (action.type) {
    default:
      return state;
  }
}

因為我們會有兩份 Reducer,要用 combineReducers 將兩者合併,目前還沒做到 Filter Reducer,這邊就先填上 Todo Reducer。

reducer/index.js

import { combineReducers } from 'redux';

import todosReducer from './todos';

const todoApp = combineReducers({
    todosReducer
});

export default todoApp;

Create Store

再來建立 Store 引入 rootReducer,透過 <Provider> 的方式來傳遞 Store 讓網頁能夠讀取更新狀態。

src/store.js

import { createStore } from "redux";
import rootReducer from "./reducer/index";

const store = createStore(rootReducer);

export default store;

src/index.js

...
import { Provider } from "react-redux";
import store from "./store";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  ...
);

Render TaskItem

將 Store 傳入 React Component 後,TaskList Component 就可以使用 useSelector 取得任務清單。原本我們是直接複製三個 <TaskItem /> 產生三個任務,修改成利用 forEach 讀取 Store 的動態產生任務,並且把任務資料和編號 task={{ ...item, idx: index }} 傳入 <TaskItem />

components/TaskList.js

...
import { useSelector } from "react-redux";

function TaskList() {
  const tasks = useSelector((store) => store.todosReducer);

  const renderItems = () => {
    let list = [];
    tasks.forEach((item, index) => {
        list.push(
          <TaskItem key={item.taskName} task={{ ...item, idx: index }} />
        );
    });
    return list;
  };

  return (
    <Wrapper>
      ...
      <TaskItemContainer>{renderItems()}</TaskItemContainer>
    </Wrapper>
  );
}

components/TaskItem.js

function TaskItem(props) {

  return (
    <Container>
      <CheckBox
        type="checkbox"
        checked={props.task.isCompleted}
      />
      <TaskName>{props.task.taskName}</TaskName>
      <Button>Delete</Button>
    </Container>
  );
}

新增 / 刪除 Task

所有任務都能動態渲染後,再來處理任務新增 / 刪除的功能,在 ActionTypes.js 定義 ADD_TASKDELETE_TASK,這樣可以統一方便管理所有 Action。

actions/ActionTypes.js

export const ADD_TASK = 'ADD_TASK';
export const DELETE_TASK = 'DELETE_TASK';

新增 Task

Step1:建立 Action Creator 產生新增 Task 的動作 Function addTask,並取得新增的任務名稱 taskName

actions/todos.js

import * as types from './ActionTypes';

export function addTask(taskName){
    return {
        type: types.ADD_TASK,
        taskName
    };
}

Step2:處理 Todo Reducer,讀取 action.type 為 ADD_TASK 時,儲存新任務的資料並回傳新的 State。

reducer/todos.js

import * as types from '../actions/ActionTypes';

const initialTasks = ...

export default function todos(state = initialTasks, action) {
  switch (action.type) {
    case types.ADD_TASK:
      return [
        ...state,
        {
          taskName: action.taskName,
          isCompleted: false,
        },
      ];
    default:...
  }
}

Step3:完成 Action 和 Reducer 的設定後,在 AddTask Component 利用 useDispatch() 來調用 Action。點擊 <AddBtn /> 後呼叫 handleClick(),處理新增任務的程式。

components/AddTask.js

import { useDispatch } from "react-redux";

function AddTask() {
  const dispatch = useDispatch();

  const [newTask, setnewTask] = ...
  const handleChange = ...

  const handleClick = (event) => {
    if(newTask === "") return;    //檢查有沒有輸入任務名稱
    dispatch(actions.addTask(newTask));
    setnewTask("");
  };

  return (
    <Wrapper>
        ...
      <AddBtn onClick={() => handleClick()} >
        <img src={addIcon} alt=""/>
      </AddBtn>
    </Wrapper>
  );
}

刪除 Task

Step1:刪除 Task 的部分也大同小異,第一步建立 Action Creator 產生刪除 Task 的動作 Function deleteTask,並取得要刪除的任務索引值 idx

actions/todos.js

export function deleteTask(idx){
    return {
        type: types.DELETE_TASK,
        idx
    };
}

Step2:處理 Todo Reducer,讀取 action.type 為 DELETE_TASK 時,刪除該任務的資料並回傳的新 State。

actions/todos.js

export default function todos(state = initialTasks, action) {
  switch (action.type) {
    case types.ADD_TASK:...
    case types.DELETE_TASK:
      return [
        ...state.slice(0, action.idx),
        ...state.slice(action.idx + 1)
      ];
    default:...
  }
}

Step3:完成 Action 和 Reducer 的設定後,在 TaskItem Component 利用 useDispatch() 來調用 Action。點擊 <Button/> 後直接呼叫 deleteTask(),傳入要刪除的任務索引值。

components/TaskItem.js

import { useDispatch } from "react-redux";

function TaskItem(props) {
  const dispatch = useDispatch();

  return (
    <Container>
      ...
      <Button onClick={() => dispatch(actions.deleteTask(props.task.idx))}>
        Delete
      </Button>
    </Container>
  );
}


小結

今天完成了渲染任務清單、和任務新增刪除的動作,在下一篇文章,我們會繼續完成最後一個部分 — Filter 篩選器,那我們就明天見囉!

範例程式碼

如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️


上一篇
[Day 27 - 小試身手] Todolist with React (2)
下一篇
[Day 29 - 小試身手] Todolist with React (4)
系列文
Follow me! 來一場前端技能樹之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言